﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace ExperimentosConArchivos
{
    class Program
    {
        static void Main (string [] args)
        {
            int start = Environment.TickCount;
            
            const string NOMBRE_ARCHIVO = @"C:/Documents and Settings/avarela/Escritorio/verexe00230020130530000000.zip";

            crearArchivo("./verexe00230020130530000000.zip", NOMBRE_ARCHIVO, dameTamanoArchivo(NOMBRE_ARCHIVO));
            
            Console.WriteLine   ("tardo = {0} milis", Environment.TickCount - start);
            Console.ReadLine();
        }

        static void crearArchivo (string pathArchivoACrear, string nombreArchivoRemoto, int tamanoArchivo)
        {
            const int TAMANO_MAXIMO_DE_PARTE = 32768 /*64 * 1024 */; // 64 K
            string dirPartes = "PARTES_" + Path.GetFileName (pathArchivoACrear.Replace(".", "_DOT_"));

            int numeroUltimaParte = -1;

            if (! Directory.Exists(dirPartes))
            {
                Directory.CreateDirectory(dirPartes);
            }
            else
            {
                // hago un dir, buscando archivos de "parte"
                List<string> partes = new List<string>(Directory.GetFiles(
                    dirPartes, "parte_????_????????????????????????????????"));

                if (partes.Count > 0)
                {
                    partes.Sort();
                    string ultimaParte = partes[partes.Count - 1];

                    // sacar número de parte
                    // numeroUltimaParte = int.Parse(Path.GetFileName(ultimaParte).Substring(6, 4));
                    numeroUltimaParte = int.Parse(Regex.Match(Path.GetFileName(ultimaParte), "[0-9]{4}").Value);

                    // sacar hash
                    // string hash = Path.GetFileName(ultimaParte).Substring(11);
                    string hash = Regex.Match(Path.GetFileName(ultimaParte), "[0-9ABCDEF]{32}").Value;

                    // si el hash es distinto, borro el archivo
                    if (0 != hash.CompareTo(Hashes.dameString16_MD5(File.ReadAllBytes(ultimaParte))))
                    {
                        Console.WriteLine("El hash de {0} es incorrecto, reintentando...", ultimaParte);
                        File.Delete(ultimaParte);
                        numeroUltimaParte -= 1;
                    }

                    Console.WriteLine("Se intentará bajar desde la parte {0}", numeroUltimaParte + 1);
                }
                else
                {
                    Console.WriteLine("Se intentará bajar desde la parte 0");
                }
            }

            int cantidadDePartes = dameCantidadDePartes (tamanoArchivo, TAMANO_MAXIMO_DE_PARTE);

            for (int i = numeroUltimaParte+1; i < cantidadDePartes; i++)
            {
                for (; ; )
                {
                    string parteEnBase64    = dameParteEnBase64(nombreArchivoRemoto, tamanoArchivo, TAMANO_MAXIMO_DE_PARTE, i);
                    byte[] parte            = Convert.FromBase64String(parteEnBase64);
                    string hash             = Hashes.dameString16_MD5(parte);
                    string pathArchivoParte = Path.Combine(dirPartes, string.Format("parte_{0:0000}_{1}", i, hash));

                    using (FileStream fs = File.Create(pathArchivoParte))
                    {
                        fs.Write(parte, 0, parte.Length);
                        fs.Close();
                    }

                    Console.WriteLine("parte nro {0}, hash {1}", i, hash);

                    if (true /* el hash remoto es igual que el hash del archivo creado */)
                        break;
                } /* salgo a por otra parte! */
            }

            // si llegamos a esta instancia es porque tenemos todas las partes bajadas :D

            // if ( no existe el archivo temporal final )
            // { construir el archivo aca en un temporal... }
            // else
            // { 
            //  if (existe la carpeta de partes)
            //     borrar la carpeta con las partes;
            //
            //  mover el archivo a la ubicacion deseada...
            //  con File.Move
            // }

            // ok
        }

        static int dameTamanoArchivo (string nombreArchivo)
        {
            return (int) new FileInfo (nombreArchivo).Length;
        }

        static byte [] dameParte (string nombreArchivo, int tamanoArchivo, int tamanoMaximoDeParte, int posicionParte)
        {
            int     espacio = dameEspacioParaParte (tamanoArchivo, tamanoMaximoDeParte, posicionParte);
            byte [] buff    = new byte [espacio];

            using (FileStream fs = File.OpenRead (nombreArchivo))
            {
                fs.Seek (posicionParte * tamanoMaximoDeParte, SeekOrigin.Begin);
                fs.Read (buff, 0, buff.Length);
                fs.Close();
            }

            return buff;
        }

        static string dameParteEnBase64 (string nombreArchivo, int tamanoArchivo, int tamanoMaximoDeParte, int posicionParte)
        {
            byte [] buff = dameParte (nombreArchivo, tamanoArchivo, tamanoMaximoDeParte, posicionParte);
            return Convert.ToBase64String (buff);
        }

        static int dameCantidadDePartes (int tamanoArchivo, int tamanoMaximoDeParte)
        {
            return tamanoArchivo / tamanoMaximoDeParte + 
                (tamanoArchivo % tamanoMaximoDeParte > 0 ? 1 : 0);
        }

        static int dameEspacioParaParte (int tamanoArchivo, int tamanoMaximoDeParte, int posicionParte)
        {
            int tamanoVirtual   = tamanoMaximoDeParte * (posicionParte + 1);
            int sobra           = tamanoVirtual - tamanoArchivo;
            return tamanoMaximoDeParte - (tamanoArchivo < tamanoVirtual ? sobra : 0);
        }
    }
}
